// Copyright 2011 Edward Wahlen. All Rights Reserved.

package org.wahlen.util;

import com.google.common.base.Function;
import com.google.inject.util.Objects;

import java.util.Arrays;
import java.util.Iterator;

/**
 * @author edward.wahlen@gmail (Edward Wahlen)
 */
public class Pair<F, S> {

  public final F first;
  public final S second;

  protected Pair(F first, S second) {
    this.first = first;
    this.second = second;
  }

  @Override
  public String toString() {
    return "<" + first + ", " + second + ">";
  }

  @Override
  public int hashCode() {
    return Arrays.hashCode(new Object[] {first, second});
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }

    Pair pair = (Pair) o;
    return (first == null ? pair.first == null : first.equals(pair.first))
        && (second == null ? pair.second == null : second.equals(pair.second));
  }

  public static <F, S> Pair<F, S> of(F first, S second) {
    return new Pair<F, S>(first, second);
  }

  public static <F, S> Iterator<Pair<F, S>> pairUp(final Iterator<F> first,
      final Iterator<S> second) {
    return new Iterator<Pair<F, S>>() {
      public boolean hasNext() {
        return first.hasNext() && second.hasNext();
      }

      public Pair<F, S> next() {
        return Pair.of(first.next(), second.next());
      }

      public void remove() {
        first.remove();
        second.remove();
      }
    };
  }

  public static <F, S> Iterable<Pair<F, S>> pairUp(final Iterable<F> first,
      final Iterable<S> second) {
    return new Iterable<Pair<F, S>>() {
      public Iterator<Pair<F, S>> iterator() {
        return pairUp(first.iterator(), second.iterator());
      }
    };
  }

  public static <S, T, U> Function<Pair<S, T>, Pair<U, T>> onFirst(final Function<S, U> f) {
    return new Function<Pair<S, T>, Pair<U, T>>() {
      public Pair<U, T> apply(Pair<S, T> pair) {
        return Pair.of(f.apply(pair.first), pair.second);
      }
    };
  }

  public static <S, T, U> Function<Pair<S, T>, Pair<S, U>> onSecond(final Function<T, U> f) {
    return new Function<Pair<S, T>, Pair<S, U>>() {
      public Pair<S, U> apply(Pair<S, T> pair) {
        return Pair.of(pair.first, f.apply(pair.second));
      }
    };
  }
}
